/*
 * Copyright (c)  2008 Sebastien Tremblay
 *
 * id3-tag-builder - classes.Format.java
 *
 * All source and documentation is copyrighted by Sebastien Tremblay
 * (seb at ryders dot net) and made available under the Apache License 2.0.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id: Format.java 33 2010-04-02 07:47:55Z ryders $
 */
package com.empireone.id3tagbuilder.classes;

import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The <code>Format</code> class is the representation of a human-readable
 * regular expression.
 *
 * @author Sebastien Tremblay
 */
public class Format implements java.io.Serializable {

    private int id;
    private String name;
    private String expr;            // human-readable
    private Pattern exprPattern;    // rexexp compiled from the string exp
    private ArrayList<String> placeholders = new ArrayList<String>();
    private boolean isValid = true;

    /**
     * Creates a <code>Format</code> object based one the given id,
     * name and expr. If the expression cannot be translated into a compiled
     * <code>Pattern</code>, then the created <code>Format</code> will not
     * be valid.
     * @param id        unique identifier to link to this format
     * @param name      name or label to use
     * @param expr      human-readable extract expression
     */
    public Format(int id, String name, String expr) {
        this.id = id;
        this.name = name;
        this.expr = expr;

        sanitisePattern();
    }

    /**
     * @return          the RegExp <code>Pattern</code> (compiled) associated with the format.
     */
    public Pattern getPattern() {
        return exprPattern;
    }

    /**
     * @return          whether the current <code>Format</code> is valid. It
     *                  will NOT be when the expression could not be transposed
     *                  into a proper regular expression <code>Pattern</code>.
     */
    public boolean isValid() {
        return isValid;
    }

    /**
     * @return          the placeholders collection for the compiled expression
     */
    public ArrayList<String> getPlaceholders() {
        return placeholders;
    }

    /**
     * The <code>sanitisePattern</code> function is responsible for taking a
     * human-readable expression, and compiling it into a regexp. Special chars
     * suported: <code><</code> and <code>></code> to make a portion optional and many <code>:tag:</code>.
     */
    protected void sanitisePattern() {

        // index 0 in matcher.match always returns the full expression
        placeholders.add("");

        // convert the format into a regex pattern
        String pattern = expr;

        // <(:label:)> :album: <(:year:)>/:trackno: <(:trackartist:)> :title:

        // Directory Separator - backslashes should be slashes
        pattern = pattern.replace("\\", "/");
        // <(:label:)> :album: <(:year:)>\/:trackno: <(:trackartist:)> :title:

        // Then all regexp chars needs to be escaped
        pattern = pattern.replaceAll("([\\\\'\\,\\^\\$\\.\\|\\?\\*\\+\\(\\)\\{\\}\\]])", "\\\\$1");
        // <\(:label:\)> :album: <\(:year:\)>\/:trackno: <\(:trackartist:\)> :title:

        // We also have to replace the place holders by their appropriate exp
        while (pattern.indexOf(":") >= 0) {
            int first = pattern.indexOf(":");
            int last = pattern.indexOf(":", first + 1);
            if (last < 0) {
                // colons not closed
                isValid = false;
                break;
            }

            // the 'appropriate' expression depends on whether the item was
            // marked as optional or not in the format;
            // if optional: (.*?)
            // else: (.*)?
            String ph = pattern.substring(first + 1, last);
            //Pattern regex = Pattern.compile("<[^>:]*?:" + ph + ":[^<:]*?>");
            // we need to allow indented tags, like <:album:<:year:>> so that
            // we can build a hierarchy of optional items, like
            // (artist) album (disc count) (year)/disc no (album artist)/track title
            // to be
            // (artist) album (disc count) (year)</disc no< (album artist)>>/track title
            // and making the album artist optional within a optional tag.
            Pattern regex = Pattern.compile("<[^>:]*?:" + ph + ":.*?>");
            Matcher regexMatcher = regex.matcher(pattern);
            boolean opt = regexMatcher.find();

            if (opt) {
                placeholders.add("");
            }

            if (ph.equals("track")) { //pattern = pattern.replace(":"+ph+":",opt ? "(\\d{1,3})?" : "(\\d{1,3})");
                pattern = pattern.substring(0, first) + "(\\d{1,3})" + (opt ? "?" : "") + pattern.substring(last + 1);
            } else if (ph.equals("year")) //pattern = pattern.replace(":"+ph+":",opt ? "(\\d{2,4})?" : "(\\d{2,4})");
            {
                pattern = pattern.substring(0, first) + "(\\d{2,4})" + (opt ? "?" : "") + pattern.substring(last + 1);
            } else //pattern = pattern.replace(":"+ph+":",opt ? "(.*?)" : "(.*)?");
            {
                pattern = pattern.substring(0, first) + "(.*?)" + pattern.substring(last + 1);
            }

            placeholders.add(ph);
        }
        // <\((.*?)\)> (.*)? <\((.*?)\)>\/(.*)? <\((.*?)\)> (.*)?

        // the optional entries should be wrapped around a specific class
        pattern = pattern.replace(" <", "(\\s?").replace("<", "(").replace("> ", ")?\\s?").replace(">", ")?");
        // (\((.*?)\))? (.*)? (\((.*?)\))?\/(.*)? (\((.*?)\))? (.*)?

        // Replace the spaces by their apropriate regexp, and
        // finally wrap the whole expression around ^ $
        pattern = "^" + pattern.replace(" ", "\\s") + "$";

        // (\((.*?)\))? (.*)? (\((.*?)\))?\/(.*?) (\((.*?)\))? (.*)?
        // (\((.*?)\))?\s?(.*)? (\((.*?)\))?\s?\/(.*?) (\((.*?)\))?\s?(.*)?
        // (\((.*?)\))?\s?(.*)?\s(\((.*?)\))?\s?\/(.*?)\s(\((.*?)\))?\s?(.*)?
        // ^(\((.*?)\))?\s?(.*)?\s(\((.*?)\))?\s?\/(.*?)\s(\((.*?)\))?\s?(.*)?$
        if (isValid) {
            this.exprPattern = Pattern.compile(pattern);
        }

        // (\((.*?)\))? (.*)? (\((.*?)\))?\/(.*?) (\((.*?)\))? (.*)?
    }

    /**
     *
     * @return
     */
    @Override
    public String toString() {
        return name;
    }
}
